/*
 * Copyright 2013-2020 The OpenZipkin Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package brave.grpc;

import brave.rpc.RpcClientRequest;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.Metadata;
import io.grpc.Metadata.Key;
import io.grpc.MethodDescriptor;
import java.util.Map;

/**
 * Allows access gRPC specific aspects of a client request during sampling and parsing.
 *
 * @see GrpcClientResponse
 * @see GrpcRequest for a parsing example
 * @since 5.12
 */
public final class GrpcClientRequest extends RpcClientRequest implements GrpcRequest {
  final Map<String, Key<String>> nameToKey;
  final MethodDescriptor<?, ?> methodDescriptor;
  final CallOptions callOptions;
  final ClientCall<?, ?> call;
  final Metadata headers;

  GrpcClientRequest(Map<String, Key<String>> nameToKey, MethodDescriptor<?, ?> methodDescriptor,
      CallOptions callOptions, ClientCall<?, ?> call, Metadata headers) {
    if (nameToKey == null) throw new NullPointerException("nameToKey == null");
    if (methodDescriptor == null) throw new NullPointerException("methodDescriptor == null");
    if (callOptions == null) throw new NullPointerException("callOptions == null");
    if (call == null) throw new NullPointerException("call == null");
    if (headers == null) throw new NullPointerException("headers == null");
    this.nameToKey = nameToKey;
    this.methodDescriptor = methodDescriptor;
    this.callOptions = callOptions;
    this.call = call;
    this.headers = headers;
  }

  /** Returns the {@link #call()} */
  @Override public Object unwrap() {
    return call;
  }

  @Override public String method() {
    return GrpcParser.method(methodDescriptor.getFullMethodName());
  }

  @Override public String service() {
    // MethodDescriptor.getServiceName() is not in our floor version: gRPC 1.2
    return GrpcParser.service(methodDescriptor.getFullMethodName());
  }

  /**
   * Returns the {@linkplain MethodDescriptor method descriptor} passed to {@link
   * ClientInterceptor#interceptCall}.
   *
   * @since 5.12
   */
  @Override public MethodDescriptor<?, ?> methodDescriptor() {
    return methodDescriptor;
  }

  /**
   * Returns the {@linkplain CallOptions call options} passed to {@link
   * ClientInterceptor#interceptCall}.
   *
   * @since 5.12
   */
  public CallOptions callOptions() {
    return callOptions;
  }

  /**
   * Returns the {@linkplain ClientCall client call} generated by {@link Channel#newCall} during
   * {@link ClientInterceptor#interceptCall}.
   *
   * @since 5.12
   */
  public ClientCall<?, ?> call() {
    return call;
  }

  /**
   * Returns the {@linkplain Metadata headers} passed to {@link ClientCall#start(ClientCall.Listener,
   * Metadata)}.
   *
   * @since 5.12
   */
  @Override public Metadata headers() {
    return headers;
  }

  @Override protected void propagationField(String keyName, String value) {

    if (keyName == null) throw new NullPointerException("keyName == null");
    if (value == null) throw new NullPointerException("value == null");
    Key<String> key = nameToKey.get(keyName);
    if (key == null) {
      assert false : "We currently don't support setting headers except propagation fields";
      return;
    }
    headers.removeAll(key);
    headers.put(key, value);
  }
}
